package org.dbstar.scheduler.actions;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.dbstar.scheduler.beans.Job;
import org.dbstar.scheduler.beans.JobConf;
import org.dbstar.scheduler.beans.JobDependency;
import org.dbstar.scheduler.beans.JobDependencyItem;
import org.dbstar.scheduler.beans.OperateType;
import org.dbstar.scheduler.beans.ParameterDefine;
import org.dbstar.scheduler.beans.ParameterDefineType;
import org.dbstar.scheduler.control.CatalogController;
import org.dbstar.scheduler.control.JobConfController;
import org.dbstar.scheduler.control.JobController;
import org.dbstar.scheduler.control.JobDependencyController;
import org.dbstar.scheduler.control.JobSchedulerExecutor;
import org.dbstar.scheduler.control.exception.DaoException;
import org.dbstar.scheduler.identifier.Identifier;
import org.dbstar.scheduler.identifier.IdentifierFactory;
import org.dbstar.scheduler.identifier.IdentifierType;
import org.dbstar.scheduler.job.IllegalFactoryException;
import org.dbstar.scheduler.job.JobStatus;
import org.dbstar.scheduler.quartz.RunJobConfJob;
import org.dbstar.scheduler.utils.JobUtils;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.Scheduler;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;

/**
 * 根据JobConf来构造并执行一个Job
 * 
 * @author 代波
 * @since 1.0.0
 * @version 1.0.0
 * @date 2012-4-8
 */
@Results({
		@Result(name = "viewResult", location = "job-result.action", type = "redirectAction", params = { "identifier",
				"${job.identifier}" }),
		@Result(name = "viewConf", location = "job-conf.action", type = "redirectAction", params = { "identifier",
				"${identifier}", "action", "view", "message", "${message}" }),
		@Result(name = "json", type = "json", params = { "root", "jsonRoot" }) })
public class RunJobConfAction extends ActionSupport {
	private static final String RESULT_PARAM = "param";

	private Scheduler scheduler;
	private JobConfController jobConfController;
	private JobController jobController;
	private CatalogController catalogController;
	private JobSchedulerExecutor jobSchedulerExecutor;
	private IdentifierFactory identifierFactory;
	private JobDependencyController jobDependencyController;

	private Identifier identifier;
	private Identifier jobIdentifier;
	private Identifier triggerIdentifier;
	private Identifier dependencyIdentifier;
	private String paraValue[];
	private String action;
	private boolean failedOnly;
	private String execute;
	private String triggerDesc;
	private String startAt;
	private String endAt;
	private String repeatCount;
	private String interval;
	private String cron;
	private String[] dependKeys;
	private Identifier[] dependencyJob;
	private String[] dependencyJobPara;
	private String[] dependencyKeyPara;

	private String message;
	private JobConf conf;
	private Job job;
	private List<Trigger> triggers;
	private List<JobDependency> dependencies;
	private List<JobConf> confs;
	private List<JobConf> fireConfs;
	private ParameterDefineType defineTypes[];
	private AjaxResult jsonRoot;

	public void setScheduler(Scheduler scheduler) {
		this.scheduler = scheduler;
	}

	public void setJobConfController(JobConfController jobConfController) {
		this.jobConfController = jobConfController;
	}

	public JobConfController getJobConfController() {
		return jobConfController;
	}

	public void setJobController(JobController jobController) {
		this.jobController = jobController;
	}

	public void setJobSchedulerExecutor(JobSchedulerExecutor jobSchedulerExecutor) {
		this.jobSchedulerExecutor = jobSchedulerExecutor;
	}

	public CatalogController getCatalogController() {
		return catalogController;
	}

	public void setCatalogController(CatalogController catalogController) {
		this.catalogController = catalogController;
	}

	public void setIdentifierFactory(IdentifierFactory identifierFactory) {
		this.identifierFactory = identifierFactory;
	}

	public void setJobDependencyController(JobDependencyController jobDependencyController) {
		this.jobDependencyController = jobDependencyController;
	}

	public void setIdentifier(Identifier identifier) {
		this.identifier = identifier;
	}

	public Identifier getIdentifier() {
		return identifier;
	}

	public void setJobIdentifier(Identifier jobIdentifier) {
		this.jobIdentifier = jobIdentifier;
	}

	public void setTriggerIdentifier(Identifier triggerIdentifier) {
		this.triggerIdentifier = triggerIdentifier;
	}

	public void setDependencyIdentifier(Identifier dependencyIdentifier) {
		this.dependencyIdentifier = dependencyIdentifier;
	}

	public void setAction(String action) {
		this.action = action;
	}

	public void setFailedOnly(boolean failedOnly) {
		this.failedOnly = failedOnly;
	}

	public String getExecute() {
		return execute;
	}

	public void setExecute(String execute) {
		this.execute = execute;
	}

	public String getTriggerDesc() {
		return triggerDesc;
	}

	public void setTriggerDesc(String triggerDesc) {
		this.triggerDesc = triggerDesc;
	}

	public String getStartAt() {
		return startAt;
	}

	public void setStartAt(String startAt) {
		this.startAt = startAt;
	}

	public String getEndAt() {
		return endAt;
	}

	public void setEndAt(String endAt) {
		this.endAt = endAt;
	}

	public String getRepeatCount() {
		return repeatCount;
	}

	public void setRepeatCount(String repeatCount) {
		this.repeatCount = repeatCount;
	}

	public String getInterval() {
		return interval;
	}

	public void setInterval(String interval) {
		this.interval = interval;
	}

	public String getCron() {
		return cron;
	}

	public void setCron(String cron) {
		this.cron = cron;
	}

	public String[] getDependKeys() {
		return dependKeys;
	}

	public void setDependKeys(String[] dependKeys) {
		this.dependKeys = dependKeys;
	}

	public Identifier[] getDependencyJob() {
		return dependencyJob;
	}

	public void setDependencyJob(Identifier[] dependencyJob) {
		this.dependencyJob = dependencyJob;
	}

	public String[] getDependencyJobPara() {
		return dependencyJobPara;
	}

	public void setDependencyJobPara(String[] dependencyJobPara) {
		this.dependencyJobPara = dependencyJobPara;
	}

	public String[] getDependencyKeyPara() {
		return dependencyKeyPara;
	}

	public void setDependencyKeyPara(String[] dependencyKeyPara) {
		this.dependencyKeyPara = dependencyKeyPara;
	}

	public void setParaValue(String[] paraValue) {
		this.paraValue = paraValue;
	}

	public String[] getParaValue() {
		return paraValue;
	}

	public String getMessage() {
		return message;
	}

	public JobConf getConf() {
		return conf;
	}

	public Job getJob() {
		return job;
	}

	public List<Trigger> getTriggers() {
		return triggers;
	}

	public List<JobDependency> getDependencies() {
		return dependencies;
	}

	public List<JobConf> getConfs() {
		return confs;
	}

	public List<JobConf> getFireConfs() {
		return fireConfs;
	}

	public ParameterDefineType[] getDefineTypes() {
		return defineTypes;
	}

	public AjaxResult getJsonRoot() {
		return jsonRoot;
	}

	/**
	 * ajax 接口
	 * 
	 * @return
	 * @throws Exception
	 */
	public String json() throws Exception {
		jsonRoot = new AjaxResult();
		if ("execute".equals(action)) {
			if (RESULT_PARAM.equals(executeAction())) {
				jsonRoot.error(message);
			} else {
				jsonRoot.ok(this.job);
			}
		} else {
			jsonRoot.error("unknown action: " + action);
		}
		return "json";
	}

	@Override
	public String execute() throws Exception {
		String result = RESULT_PARAM;

		if ("removeTrigger".equals(action)) {
			result = removeTrigger();
		} else if ("createTrigger".equals(action)) {
			result = createTrigger();
		} else if ("removeDependency".equals(action)) {
			result = removeDependency();
		} else if ("createDependency".equals(action)) {
			result = createDependency();
		} else if ("reExecute".equals(action)) {
			result = reExecute(); // 重新执行Job
		} else if ("execute".equals(action)) {
			result = executeAction();
		}

		if (RESULT_PARAM.equals(result)) {
			if (conf == null && identifier != null) {
				conf = jobConfController.getJobConf(identifier);
			}

			triggers = new ArrayList<Trigger>();
			for (TriggerKey key : scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(identifier
					.getStringIdentifier()))) {
				triggers.add(scheduler.getTrigger(key));
			}
			dependencies = jobDependencyController.getJobDependencies(identifier);
			defineTypes = ParameterDefineType.values();
			confs = jobConfController.getAllJobConfs();
			fireConfs = new ArrayList<JobConf>();
			for (JobDependency dependency : jobDependencyController.getAllJobDependencies()) {
				if (containJob(dependency, identifier)) {
					JobConf c = jobConfController.getJobConf(dependency.getJobConfIdentifier());
					if (c != null && !fireConfs.contains(c)) {
						fireConfs.add(c);
					}
				}
			}

			// 按照模版创建Job，复制参数
			if (conf.getParameterSize() > 0 && jobIdentifier != null) {
				Job j = jobController.getJob(jobIdentifier);
				if (j != null) {
					Map<String, String> parameters = j.getParameters();
					paraValue = new String[conf.getParameterSize()];
					int index = 0;
					for (ParameterDefine pd : conf.getParameters()) {
						String val = parameters == null ? null : parameters.get(pd.getName());
						paraValue[index++] = val == null ? "" : val;
					}
				}
			}
		}

		return result;
	}

	private static boolean containJob(JobDependency dep, Identifier identifier) {
		for (JobDependencyItem item : dep.getDependencies()) {
			if (identifier.equals(item.getIdentifier())) {
				return true;
			}
		}
		return false;
	}

	private String removeTrigger() throws Exception {
		if (identifier != null && triggerIdentifier != null) {
			TriggerKey key = TriggerKey.triggerKey(triggerIdentifier.getStringIdentifier(),
					identifier.getStringIdentifier());
			Trigger trigger = scheduler.getTrigger(key);
			if (trigger != null) {
				String submitter = trigger.getJobDataMap().getString("job.submitter");
				if (!submitter.equals(user) && !isSuperVisor()) {
					message = "Access Denied.";
					return "viewConf";
				}

				scheduler.unscheduleJob(key);
				message = "调度已删除：" + triggerIdentifier.getStringIdentifier();
				operate(OperateType.trigger_remove,
						identifier.getStringIdentifier() + ':' + triggerIdentifier.getStringIdentifier());

				// 按照被删除的调度信息来设置参数
				setByTrigger(trigger);
			}
		}
		return RESULT_PARAM;
	}

	private String createTrigger() throws Exception {
		if (identifier != null && triggerIdentifier != null) {
			TriggerKey key = TriggerKey.triggerKey(triggerIdentifier.getStringIdentifier(),
					identifier.getStringIdentifier());
			Trigger trigger = scheduler.getTrigger(key);
			if (trigger != null) {
				// 按照现有调度信息来设置参数
				setByTrigger(trigger);
			}
		}
		return RESULT_PARAM;
	}

	private void setByTrigger(Trigger trigger) throws Exception {
		if (CronTrigger.class.isInstance(trigger)) {
			this.execute = "cron";
			this.cron = ((CronTrigger) trigger).getCronExpression();
		} else if (SimpleTrigger.class.isInstance(trigger)) {
			this.execute = "simple";
			this.repeatCount = String.valueOf(((SimpleTrigger) trigger).getRepeatCount());
			this.interval = String.valueOf(((SimpleTrigger) trigger).getRepeatInterval());
		}
		this.triggerDesc = trigger.getDescription();
		if (trigger.getStartTime() != null) {
			this.startAt = DateFormatUtils.format(trigger.getStartTime(), "yyyy-MM-dd HH:mm:ss");
		}
		if (trigger.getEndTime() != null) {
			this.endAt = DateFormatUtils.format(trigger.getEndTime(), "yyyy-MM-dd HH:mm:ss");
		}

		this.conf = jobConfController.getJobConf(identifier);
		@SuppressWarnings("unchecked")
		Map<String, String> pms = (Map<String, String>) trigger.getJobDataMap().get(RunJobConfJob.JOB_PARAMETERS_KEY);
		if (conf.getParameterSize() > 0 && pms != null && pms.size() > 0) {
			// 恢复参数
			paraValue = new String[conf.getParameterSize()];
			int index = 0;
			for (ParameterDefine pd : conf.getParameters()) {
				String val = pms.get(pd.getName());
				paraValue[index++] = val == null ? "" : val;
			}
		}
	}

	private String removeDependency() throws Exception {
		if (dependencyIdentifier != null) {
			JobDependency dependency = jobDependencyController.getJobDependency(dependencyIdentifier);
			if (dependency != null) {
				this.identifier = dependency.getJobConfIdentifier();
				if (!dependency.getOwner().equals(user) && !isSuperVisor()) {
					message = "Access Denied.";
					return "viewConf";
				}

				jobDependencyController.removeJobDependency(dependencyIdentifier);
				message = "依赖触发已删除：" + dependencyIdentifier.getStringIdentifier();
				operate(OperateType.depend_remove,
						identifier.getStringIdentifier() + ':' + dependencyIdentifier.getStringIdentifier());

				// 按照被删除的依赖触发信息来设置参数
				setByDependency(dependency);
			}
		}
		return RESULT_PARAM;
	}

	private String createDependency() throws Exception {
		if (dependencyIdentifier != null) {
			JobDependency dependency = jobDependencyController.getJobDependency(dependencyIdentifier);
			if (dependency != null) {
				this.identifier = dependency.getJobConfIdentifier();
				// 按照现有依赖触发信息来设置参数
				setByDependency(dependency);
			}
		}
		return RESULT_PARAM;
	}

	private void setByDependency(JobDependency dependency) throws DaoException {
		this.execute = "depend";
		this.triggerDesc = dependency.getDescription();
		if (dependency.getKeySize() > 0) {
			this.dependKeys = dependency.getKeys().toArray(new String[dependency.getKeySize()]);
		}

		this.conf = jobConfController.getJobConf(identifier);
		if (conf.getParameterSize() > 0 && dependency.getParameterSize() > 0) {
			// 恢复参数
			Map<String, String> pms = dependency.getParameters();
			paraValue = new String[conf.getParameterSize()];
			int index = 0;
			for (ParameterDefine pd : conf.getParameters()) {
				String val = pms.get(pd.getName());
				paraValue[index++] = val == null ? "" : val;
			}
		}

		// 设置依赖项
		List<Identifier> djs = new ArrayList<Identifier>();
		List<String> dks = new ArrayList<String>();
		List<String> dps = new ArrayList<String>();

		for (JobDependencyItem item : dependency.getDependencies()) {
			djs.add(item.getIdentifier());
			if (item.getParameterSize() > 0) {
				Map<String, String> pms = item.getParameters();
				if (dependency.getKeySize() > 0) {
					for (String key : dependency.getKeys()) {
						dks.add(pms.get(key));
					}
				}
			}
			dps.add(item.getCondition());
		}

		this.dependencyJob = djs.toArray(new Identifier[djs.size()]);
		this.dependencyKeyPara = dks.toArray(new String[dks.size()]);
		this.dependencyJobPara = dps.toArray(new String[dps.size()]);
	}

	private String reExecute() throws Exception {
		job = jobController.getJob(jobIdentifier);
		if (job.reExecute(failedOnly, jobController)) {
			jobController.updateJob(job);
			jobController.changeJobStatus(job.getIdentifier(), JobStatus.submit);
			job = jobController.getJob(jobIdentifier);
			operate(OperateType.job_reExecute, jobIdentifier);
		}
		return "viewResult";
	}

	private String executeAction() throws Exception {
		conf = jobConfController.getJobConf(identifier);

		Map<String, String> parameters = null;
		if (conf.getParameterSize() > 0) {
			// 设置参数
			int index = 0;
			parameters = new HashMap<String, String>();
			for (ParameterDefine pd : conf.getParameters()) {
				String val = paraValue[index++];
				if (!StringUtils.isBlank(val)) {
					parameters.put(pd.getName(), val.trim());
				}
			}

			// 依赖触发执行时，关键参数不用赋值，所以填入一个默认值。
			if ("depend".equals(execute)) {
				if (dependKeys != null) {
					for (String key : dependKeys) {
						parameters.put(key, "-");
					}
				}
			}
		}

		try {
			parameters = JobUtils.getJobParameters(conf, parameters, false);
		} catch (IllegalArgumentException e) {
			message = e.getLocalizedMessage();
			return RESULT_PARAM;
		}

		if ("now".equals(execute)) {
			this.job = jobSchedulerExecutor.submit(identifier, parameters, user);
			operate(OperateType.job_execute, job.getIdentifier());
			return "viewResult";
		} else if ("simple".equals(execute) || "cron".equals(execute)) {
			try {
				Trigger trigger = buildTrigger(parameters);
				scheduler.scheduleJob(trigger);
				operate(OperateType.trigger_add, trigger.getKey().getGroup() + ':' + trigger.getKey().getName());
				return "viewConf";
			} catch (Throwable e) {
				message = "无法定制调度：" + e.getLocalizedMessage();
			}
		} else if ("depend".equals(execute)) {
			return executeDepend(parameters);
		} else {
			message = "未知的执行方式：" + execute;
		}

		return RESULT_PARAM;
	}

	private Trigger buildTrigger(Map<String, String> parameters) throws Throwable {
		TriggerBuilder<Trigger> builder = TriggerBuilder.newTrigger().forJob(RunJobConfJob.class.getName());

		parseIdentity(builder);
		parseDescription(builder);

		DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		parseStartTime(builder, format);
		parseEndTime(builder, format);

		if ("simple".equals(execute)) {
			parseSimpleSchedule(builder);
		} else {
			parseCronSchedule(builder);
		}

		JobDataMap dataMap = new JobDataMap();
		dataMap.put(Identifier.class.getName(), this.conf.getIdentifier());
		dataMap.put(RunJobConfJob.JOB_SUBMITTER_KEY, user);
		if (parameters != null && !parameters.isEmpty()) {
			dataMap.put(RunJobConfJob.JOB_PARAMETERS_KEY, parameters);
		}
		builder.usingJobData(dataMap);

		return builder.build();
	}

	private TriggerBuilder<Trigger> parseIdentity(TriggerBuilder<Trigger> builder) throws IllegalFactoryException {
		return builder.withIdentity(identifierFactory.createIdentifier(IdentifierType.trigger).getStringIdentifier(),
				conf.getIdentifier().getStringIdentifier());
	}

	private TriggerBuilder<Trigger> parseDescription(TriggerBuilder<Trigger> builder) {
		return StringUtils.isBlank(triggerDesc) ? builder : builder.withDescription(triggerDesc);
	}

	private TriggerBuilder<Trigger> parseStartTime(TriggerBuilder<Trigger> builder, DateFormat format)
			throws ParseException {
		if (StringUtils.isBlank(startAt)) {
			return builder.startNow();
		} else {
			try {
				return builder.startAt(format.parse(startAt));
			} catch (Exception e) {
				throw new IllegalArgumentException("无效的[开始时间]值：" + e.getLocalizedMessage());
			}
		}
	}

	private TriggerBuilder<Trigger> parseEndTime(TriggerBuilder<Trigger> builder, DateFormat format)
			throws ParseException {
		if (StringUtils.isBlank(endAt)) {
			return builder;
		} else {
			try {
				return builder.endAt(format.parse(endAt));
			} catch (Exception e) {
				throw new IllegalArgumentException("无效的[结束时间]值：" + e.getLocalizedMessage());
			}
		}
	}

	private TriggerBuilder<? extends Trigger> parseSimpleSchedule(TriggerBuilder<Trigger> builder) {
		int intervalValue;
		try {
			intervalValue = Integer.parseInt(interval);
		} catch (Throwable e) {
			throw new IllegalArgumentException("无效的[重复间隔]值：" + e.getLocalizedMessage());
		}
		if (intervalValue < 0) {
			throw new IllegalArgumentException("无效的[重复间隔]值：必须>=0");
		}

		SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule()
				.withIntervalInSeconds(intervalValue);

		if (StringUtils.isBlank(repeatCount)) {
			schedBuilder.repeatForever();
		} else {
			int repeatCountValue;
			try {
				repeatCountValue = Integer.parseInt(repeatCount);
			} catch (Throwable e) {
				throw new IllegalArgumentException("无效的[执行次数]值：" + e.getLocalizedMessage());
			}
			if (repeatCountValue < 1) {
				throw new IllegalArgumentException("无效的[执行次]值：必须>=1");
			}

			schedBuilder.withRepeatCount(repeatCountValue - 1);
		}

		return builder.withSchedule(schedBuilder);
	}

	private TriggerBuilder<? extends Trigger> parseCronSchedule(TriggerBuilder<Trigger> builder) {
		CronScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule(cron);

		return builder.withSchedule(schedBuilder);
	}

	private String executeDepend(Map<String, String> parameters) throws DaoException {
		if (dependencyJob == null || dependencyJob.length == 0) {
			message = "至少要有一个依赖项";
			return RESULT_PARAM;
		}

		// TODO 目前只支持单一依赖项触发
		if (dependencyJob.length != 1) {
			message = "目前只支持单一依赖项触发";
			return RESULT_PARAM;
		}

		JobDependency dependency = new JobDependency();
		dependency.setJobConfIdentifier(identifier);
		dependency.setName(conf.getName());
		dependency.setDescription(triggerDesc);
		if (dependKeys != null) {
			dependency.setKeys(Arrays.asList(dependKeys));

			// 从参数中去掉关键参数
			for (String key : dependKeys) {
				parameters.remove(key);
			}
		}
		dependency.setParameters(parameters);

		Set<JobDependencyItem> items = new HashSet<JobDependencyItem>();

		int keyParaIndex = 0;
		int condParaIndex = 0;
		for (Identifier depConfId : dependencyJob) {
			Map<String, String> itemParams = new HashMap<String, String>();

			// 设置关键参数
			if (dependKeys != null) {
				for (String key : dependKeys) {
					String val = dependencyKeyPara[keyParaIndex++];
					if (StringUtils.isBlank(val)) {
						message = "依赖项 " + (items.size() + 1) + " 的关键参数条件 " + key + " 必须设定一个有效值";
						return RESULT_PARAM;
					} else {
						itemParams.put(key, val.trim());
					}
				}
			}

			// 设置匹配条件
			JobConf depConf = jobConfController.getJobConf(depConfId);
			if (depConf == null) {
				message = "依赖项 " + (items.size() + 1) + " 对应的Job定义不存在：" + depConfId.getStringIdentifier();
				return RESULT_PARAM;
			}

			String condition = null;
			String val = dependencyJobPara[condParaIndex++];
			if (depConf.getParameterSize() > 0) {
				if (!StringUtils.isBlank(val)) {
					condition = val.trim();
				}
			}

			items.add(new JobDependencyItem(depConfId, true, itemParams, condition));
		}
		dependency.setDependencies(items);
		dependency.setOwner(user);

		Identifier dependIdentifier = jobDependencyController.addDependency(dependency);
		operate(OperateType.depend_add, identifier.getStringIdentifier() + ':' + dependIdentifier.getStringIdentifier());
		message = "Dependency created: " + dependIdentifier.getStringIdentifier();
		return "viewConf";
	}

}
